{
 "cells": [
  {
   "cell_type": "code",
   "execution_count": 1,
   "id": "cac161e7-7f7f-4aaf-9da6-e6297b267d86",
   "metadata": {},
   "outputs": [],
   "source": [
    "#引入模块\n",
    "import os\n",
    "import random\n",
    "import torch\n",
    "from torch.utils.data import Dataset,DataLoader\n",
    "import torch.nn as nn\n",
    "from tqdm import tqdm\n",
    "import numpy as numpy\n",
    "import gc#回收内存，节约资源"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 2,
   "id": "e671e5fb-1b2b-4ba4-a306-a09b99b5738f",
   "metadata": {},
   "outputs": [],
   "source": [
    "#设置随机种子。复现试验结果，当做模板来用\n",
    "#CUDA的默认操作\n",
    "def same_seed(seed):\n",
    "    torch.backends.cudnn.deterministic =True\n",
    "    torch.backends.cudnn.benchmark =False\n",
    "    np.random.seed(seed)\n",
    "    #用cpu训练模型，为cpu设置随机种子\n",
    "    torch.manual_seed(seed)\n",
    "    if torch.cuda.is_available():\n",
    "        torch.cuda.manual_seed_all(seed)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 3,
   "id": "bb2eb9cb-7939-4332-810c-fe11744ada5b",
   "metadata": {},
   "outputs": [],
   "source": [
    "#读取pt文件（加载特征）\n",
    "def load_feat(path):\n",
    "    feat=torch.load(path)\n",
    "    return feat\n",
    "\n",
    "#拼接frame\n",
    "def shift(x,n):\n",
    "    if n<0:\n",
    "        left=x[0].repeat(-n,1)\n",
    "        right=x[:n]\n",
    "    elif n>0:\n",
    "        right=x[-1].reshape(n,1)\n",
    "        left=x[n:]\n",
    "    else:\n",
    "        return x\n",
    "    return torch.cat((left,right),dim=0)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 4,
   "id": "baecee43-b7c8-472e-9b29-3734c5d31378",
   "metadata": {},
   "outputs": [],
   "source": [
    "#拼接frame;\n",
    "#提高准确率，会把左边和右边的帧拼接在一起\n",
    "#T个帧\n",
    "'''\n",
    "x: .pt(T,39)\n",
    "concat_n:拼接的frame树目\n",
    "'''\n",
    "def concat_feat(x,concat_n):\n",
    "    assert concat_n%2==1\n",
    "    if concat_n<2:\n",
    "        return x\n",
    "        \n",
    "    seq_len=x.size(0)#frame的个数T,T个帧\n",
    "    feature_dim=x.size(1)#每一个frame的维度39\n",
    "\n",
    "    x=x.repeat(1,concat_n)#x沿着第一维（竖直）重复n次\n",
    "\n",
    "    '''\n",
    "    经过view，x成为[seq_len,concat_n,feature_dim]\n",
    "    permute调换维度的顺序。之后变为[concat_n,seq_len,feature_dim]\n",
    "    '''\n",
    "    x=x.view(seq_len,concat_n,feature_dime).permute(1,0,2)\n",
    "    \n",
    "    mid=(concat_n//2)#找出中间待预测的帧\n",
    "\n",
    "    for r_idx in range(1,mid+1):\n",
    "        x[mid+r_idx,:]=shift(x[mid+r_idx],r_idx)\n",
    "        x[mid-r_idx,:]=shift(x[mid-r_idx],-r_idx)\n",
    "\n",
    "    return x.permute(1,0,2).view(seq_len,concat_n*feature_dim)"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 9,
   "id": "063e1d19-9caf-4890-9ad8-9f462ee1c4e3",
   "metadata": {},
   "outputs": [],
   "source": [
    "#预处理数据\n",
    "def preprocess_data(split,feat_dir,phone_path,concat_nframes,train_ratio=0.8,train_val_seed=1337):\n",
    "    class_num=41#音素有固定41类\n",
    "    mode='train' if(split=='train' or split=='val') else 'test'\n",
    "\n",
    "    label_dict={}#索引字典\n",
    "    if mode!='test':#在训练和验证模式需要读取label\n",
    "        #readlines返回列表，且每个元素后有个换行符\n",
    "        phone_file=open(os.path.join(phone_path,f'{mode}_labels.txt')).readlines()#逐行读取\n",
    "        for line in phone_file:\n",
    "            line=line.strip('\\n').split(' ')\n",
    "            #读取每行第一个=（强制类型转换）每行第一个到最后一个\n",
    "            label_dict[line[0]]=[int(p) for p in line[1:]] \n",
    "\n",
    "    '''划分数据集'''\n",
    "    #这段代码拿到的是文件名\n",
    "    if split == 'train' or split=='val':\n",
    "        #train_split和test_split保存的是每一笔数据对应的文件名\n",
    "        usage_list=open(os.path.join(phone_path,'train_split.txt')).readlines()\n",
    "        random.seed(train_val_seed)\n",
    "        random.shuffle(usage_list)\n",
    "        percent=int(len(usage_list)*train_ratio)\n",
    "        if split=='train':\n",
    "            usage_list=usage_list[:percent]\n",
    "        elif split=='val':\n",
    "            usage_list=usage_list[percent:]\n",
    "    elif split=='test':#测试文件不用划分\n",
    "        usage_list=open(os.path.join(phone_path,'test_split.txt')).readlines()\n",
    "    else:\n",
    "        raise ValueError('Invalid \\'split\\' argument for dataset:PhoneDataset!')\n",
    "    #readlines读取后返回一个列表，且列表元素末尾有换行符\n",
    "    usage_list=[list.strip('\\n') for line in usage_list]\n",
    "    #打印一下多少个phoneme类别和这个数据集多少条语音\n",
    "    print('[Dataset] -#Phone Class:'+str(class_num)+',number of utterance for'+split+':'+str(len(usage_list)))\n",
    "\n",
    "    max_len=3000000\n",
    "    x=torch.empty(max_len,39*concat_nframes)#创建一个空的张量。用来存储前后相加后的frames\n",
    "    if mode!='test':#在训练或验证，需要用到label，建立一个张量储存label\n",
    "        y=torch.empty(max_len,dtype=torch.long)\n",
    "\n",
    "    idx=0\n",
    "    for i,fname in tqdm(enumerate(usage_list)):#给每一个文件名加上下标，i是序号，fname是文件名字\n",
    "        feat=load_feat(os.path.join(feat_dir,mode,f'{frame}.pt'))#读取pt文件\n",
    "        cur_len=len(feat)#当前文件长度\n",
    "        feat=concat_feat(feat,concat_nframes)\n",
    "        if mode!='test':\n",
    "            '''将train_split.txt和test_split.txt的语音名字\n",
    "            如2007-149877-0023作为索引，得到对应的label值'''\n",
    "            label=torch.LongTensor(label_dict(fname))\n",
    "        \n",
    "        x[idx:idx+cur_len]=feat\n",
    "        if mode!='test':\n",
    "            y[idx:idx+cur_len]=label\n",
    "\n",
    "        idx+=curlen\n",
    "\n",
    "    x=x[:idx,:]#已经完成将检测帧和左右帧排在一起\n",
    "    if mode!='test':\n",
    "        y=y[:idx]\n",
    "\n",
    "    print(f\"[INFO]{split} set\")\n",
    "    print(x.shape)\n",
    "    if mode!='test':\n",
    "        print(y.shape)\n",
    "        return x,y\n",
    "    else:\n",
    "        return x     "
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 13,
   "id": "40f24b07-99da-44f5-9bd4-3f3b7e727f38",
   "metadata": {},
   "outputs": [],
   "source": [
    "'''老三样'''\n",
    "#Dataset\n",
    "class LibriDataset(Dataset):\n",
    "    def __init__(self,x,y=None):\n",
    "        self.feature=x\n",
    "        if y is not None:\n",
    "            self.label=torch.LongTensor(y)\n",
    "        else:\n",
    "            self.label=None\n",
    "\n",
    "    def __getitem__(self,idx):\n",
    "        if self.label is not None:\n",
    "            return self.feature[idx],self.label[idx]\n",
    "        else:\n",
    "            return self.feature[idx]\n",
    "\n",
    "    def __len__(self):\n",
    "        return len(self.feature)\n",
    "        \n",
    "#Model:此模型是模块化模型，把hidden-layer封装起来，想构建多少层修改循环趟数就可以\n",
    "class BasicBlock(nn.Module):\n",
    "    def __init__(self,input_dim,output_dim):\n",
    "        super(BasicBlock,self).__init__()\n",
    "        self.block=nn.Sequential(\n",
    "            nn.Linear(input_dim,output_dim),#加一层全连接层\n",
    "            nn.ReLU()#加一层relu激活函数\n",
    "        )\n",
    "    def forward(self,x):\n",
    "        x=self.block(x)\n",
    "        return x\n",
    "\n",
    "\n",
    "'''\n",
    "*args *+参数，可以将不定数量的参数传递给一个函数，可以用来发送一个非键值的可变数量的参数列表给这个函数.\n",
    "'''\n",
    "    #如何用basicblock构造完整的神经网路\n",
    "class Classifier(nn.Module):\n",
    "    #输入维度，输出41个，因为有41个音标。hidden_layers是往里面添加多少个隐藏层。hidden_dim隐藏层维度\n",
    "    def __init__(self,input_dim,output_dim=41,hidden_layers=1,hidden_dim=256):\n",
    "        super(Classifier,self).__init__()\n",
    "        self.fc=nn.Sequential(\n",
    "            BasicBlock(input_dim,hidden_dim),#输入层\n",
    "                \"\"\"\n",
    "                 展开之后是这个样子\n",
    "                temp=[]\n",
    "                for _ in range(hidden_layers):#控制传多少hidden_layer\n",
    "                temp.append([BasicBlock(hidden_dim,hidden_dim))\n",
    "                \"\"\"\n",
    "            *[BasicBlock(hidden_dim,hidden_dim) for _ in ranage(hidden_layers)]#隐藏层\n",
    "            ,nn.Linear(hidden_dim,output_dim)#输出层\n",
    "        )\n",
    "\n",
    "    def forward(self,x):\n",
    "        x=self.fc(x)\n",
    "        return x\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 16,
   "id": "dadd03f4-c68b-463f-ad33-dcc45cbdd39f",
   "metadata": {},
   "outputs": [],
   "source": [
    "'''参数定义'''\n",
    "#select device\n",
    "device='cuda'if torch.cuda.is_available() else 'cpu'\n",
    "\n",
    "#data parameters\n",
    "concat_nframes=3#一共多少个帧连在一起，必须为奇数\n",
    "train_ratio=0.8\n",
    "\n",
    "#training parameters\n",
    "config={\n",
    "    'seed':0,\n",
    "    'batch_size':512,\n",
    "    'n_epoch':5,\n",
    "    'learning_rate':0.0001,\n",
    "    'model_path':'./model.ckpt'\n",
    "}\n",
    "\n",
    "#model parameters\n",
    "input_dim=39*concat_nframes\n",
    "hidden_layers=3\n",
    "hidden_dim=256\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": 17,
   "id": "a5f4733d-de5e-40ac-b903-5b7c5fda910b",
   "metadata": {},
   "outputs": [
    {
     "ename": "SyntaxError",
     "evalue": "'(' was never closed (986239911.py, line 6)",
     "output_type": "error",
     "traceback": [
      "\u001b[1;36m  Cell \u001b[1;32mIn[17], line 6\u001b[1;36m\u001b[0m\n\u001b[1;33m    optimizer=torch.optim.AdamW(model.parameters(),lr=config['learning_rate']\u001b[0m\n\u001b[1;37m                               ^\u001b[0m\n\u001b[1;31mSyntaxError\u001b[0m\u001b[1;31m:\u001b[0m '(' was never closed\n"
     ]
    }
   ],
   "source": [
    "\"\"\"训练过程\"\"\"\n",
    "#训练数据，验证数据，训练数据的加载器，验证数据的加载器，\n",
    "def trainer(train_set,val_set,train_loader,val_loader,config,model,device):\n",
    "    criterion=nn.CrossEntropyLoss()\n",
    "    #优化器区别于SGD，能够在训练过程中自适应地调整学习率\n",
    "    optimizer=torch.optim.AdamW(model.parameters(),lr=config['learning_rate'])\n",
    "    best_acc=0.0#用来保存预测正确的样本数，也就是预测对了多少个frame\n",
    "    n_epoch=config['n_epoch']\n",
    "\n",
    "    for epoch in range(num_epoch):\n",
    "    train_acc = 0.0\n",
    "    train_loss = 0.0\n",
    "    val_acc = 0.0\n",
    "    val_loss = 0.0\n",
    "    \n",
    "    # training\n",
    "    model.train() # set the model to training mode\n",
    "    for i, batch in enumerate(tqdm(train_loader)):\n",
    "        features, labels = batch\n",
    "        features = features.to(device)\n",
    "        labels = labels.to(device)\n",
    "        \n",
    "        optimizer.zero_grad() \n",
    "        outputs = model(features) \n",
    "        \n",
    "        loss = criterion(outputs, labels)\n",
    "        loss.backward() \n",
    "        optimizer.step() \n",
    "        \n",
    "        _, train_pred = torch.max(outputs, 1) # get the index of the class with the highest probability\n",
    "        train_acc += (train_pred.detach() == labels.detach()).sum().item()\n",
    "        train_loss += loss.item()\n",
    "    \n",
    "    # validation\n",
    "    if len(val_set) > 0:\n",
    "        val_acc=0.0\n",
    "        val_loss=0.0\n",
    "        model.eval() # set the model to evaluation mode\n",
    "        with torch.no_grad():\n",
    "            for i, batch in enumerate(tqdm(val_loader)):\n",
    "                features, labels = batch\n",
    "                features = features.to(device)\n",
    "                labels = labels.to(device)\n",
    "                outputs = model(features)\n",
    "                \n",
    "                loss = criterion(outputs, labels) \n",
    "                #\n",
    "                _, val_pred = torch.max(outputs, 1) \n",
    "                val_acc += (val_pred.cpu() == labels.cpu()).sum().item() # get the index of the class with the highest probability\n",
    "                val_loss += loss.item()\n",
    "\n",
    "            print('[{:03d}/{:03d}] Train Acc: {:3.6f} Loss: {:3.6f} | Val Acc: {:3.6f} loss: {:3.6f}'.format(\n",
    "                epoch + 1, num_epoch, train_acc/len(train_set), train_loss/len(train_loader), val_acc/len(val_set), val_loss/len(val_loader)\n",
    "            ))\n",
    "\n",
    "            # if the model improves, save a checkpoint at this epoch\n",
    "            if val_acc > best_acc:\n",
    "                best_acc = val_acc\n",
    "                torch.save(model.state_dict(), model_path)\n",
    "                print('saving model with acc {:.3f}'.format(best_acc/len(val_set)))\n",
    "    else:\n",
    "        print('[{:03d}/{:03d}] Train Acc: {:3.6f} Loss: {:3.6f}'.format(\n",
    "            epoch + 1, num_epoch, train_acc/len(train_set), train_loss/len(train_loader)\n",
    "        ))\n",
    "\n",
    "# if not validating, save the last epoch\n",
    "if len(val_set) == 0:\n",
    "    torch.save(model.state_dict(), model_path)\n",
    "    print('saving model at last epoch')"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "ec5b3684-c322-4296-abef-e72734a771a7",
   "metadata": {},
   "outputs": [],
   "source": [
    "\"\"\"训练准备\"\"\"\n",
    "#same seed\n"
   ]
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "e6478938-b8cc-4525-8d0d-fb9ff215088c",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "c0a5edcd-d047-4633-8eb5-97c2d0bc53a2",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "4582533d-e254-42da-b263-6adcd74c3ad9",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "79cfd695-8d9a-412c-a439-189467f942b2",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "6cdebacf-947c-4cbf-96ed-17d0ccf8dad0",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "b75c01ec-3ce1-403d-8fc0-26fc0de20bca",
   "metadata": {},
   "outputs": [],
   "source": []
  },
  {
   "cell_type": "code",
   "execution_count": null,
   "id": "f52bf178-5999-4e76-b3dd-77ddd81af02a",
   "metadata": {},
   "outputs": [],
   "source": []
  }
 ],
 "metadata": {
  "kernelspec": {
   "display_name": "yolov8",
   "language": "python",
   "name": "yolov8"
  },
  "language_info": {
   "codemirror_mode": {
    "name": "ipython",
    "version": 3
   },
   "file_extension": ".py",
   "mimetype": "text/x-python",
   "name": "python",
   "nbconvert_exporter": "python",
   "pygments_lexer": "ipython3",
   "version": "3.11.9"
  }
 },
 "nbformat": 4,
 "nbformat_minor": 5
}
